# -*- coding: utf-8 -*-

"""
@author: Laowang
@contact: QQ:1125564921
@Created on: 2022/6/20
@Remark: 
"""

import threading
import time

from itertools import chain
from queue import Queue, Empty

from typing import Dict, List, Optional, Callable, Any
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor
from framework_strategy.gopy.core.actor import MultiActors, Actor
from framework_strategy.gopy.core.sub import Exchange

# from framework_strategy.gopy.base.gateway import BaseData


class EventEngineExit(Exception):
    ...


@dataclass
class Event:
    """
    Event object consists of a type string which is used
    by event engine for distributing event, and a data
    object which contains the real data.
    """

    event_type: Optional[str]
    event_data: Any


CallableHandleType = Callable[[Event], None]
# Cardiac events
HEARTBEATEVENT = "PING"


class EventEngine(MultiActors):
    """
    The event engine is mainly used to register different callback events, 
    and events are triggered by different event types  
    Do route distribution to complete the call, 
    the whole call process is asynchronous 
    """

    # The time the heartbeat program detects
    _interval: Optional[int] = 3

    def __init__(self) -> None:
        # Program switch
        self._active: bool = False

        # The alive state of a program
        self._timer: threading.Thread = threading.Thread(
            target=self._run_timer, name="EventEngine_run_time", daemon=True)
        # Concurrency pool control callback function
        self._executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=300)

        # Callback function management
        self._handles_dafault: Exchange = Exchange()
        # Generic callback functions
        self._general_handles: List = []
        # lock
        self._lock = threading.Lock()
        self._handle_cond = threading.Condition(self._lock)
        self._general_cond = threading.Condition(self._lock)
        super().__init__(error=EventEngineExit, name="EventEngine_run")

    def stop(self):
        """ """

        self._active = False
        self.close()
        super().stop()
        super().join()
        self._timer.join()
        print("[EventEngine] stop all")

    def close(self):
        """ """
        self.put(EventEngineExit)

    def start(self):
        """ """
        self._active = True
        super().start()
        self._timer.start()

    def run(self):
        """ """

        for event in self.recv():
            if event is self._error:
                raise self._error()
            self._process(event)

    def _process(self, event: Event):
        """ """
        for handle in chain(self._handles_dafault[event.event_type], self._general_handles):
            self.handle_event(handle, event)

    def handle_event(self, handle: CallableHandleType, event: Event):
        """ """
        self._executor.submit(handle, event)
        # handle(event)

    def _run_timer(self):
        """ Send heartbeat messages regularly """
        for _ in iter(lambda: self._active, False):

            time.sleep(self._interval)
            event = Event(HEARTBEATEVENT, Any)
            try:
                self.put(event)
            except Empty as e:
                pass
            except Exception as e:
                pass
        print("[_run_timer]  exit ....")

    def put(self, event: Event):
        """ """
        super().put(event, timeout=self._interval)

    def get(self) -> Event:
        """ """
        return super().get(timeout=self._interval)

    def on_bind(self, event_type: str, handle: CallableHandleType):
        """ """
        with self._handle_cond:
            self._handles_dafault[event_type] = handle

    def out_bind(self, event_type: str, handle: CallableHandleType):
        """ """
        with self._handle_cond:
            self._handles_dafault.out_bind(event_type, handle)

    def handles(self, event_type: str = "") -> Any:
        """ """
        return self._handles_dafault.handles(event_type)

    def general_handles(self):
        """ """
        return self._general_handles

    def register_handle(self, handle: CallableHandleType):
        """ """
        with self._general_cond:
            self._general_handles.append(handle)

    def unregister_handle(self, handle: CallableHandleType):
        """ """
        with self._general_cond:
            self._general_handles.remove(handle)
